// (c) Copyright 2013-2014 Stephen Gold <sgold@sonic.net>
// Distributed under the terms of the GNU General Public License

/*
 This file is part of the Bats Game.

 The Bats Game is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by the
 Free Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 The Bats Game is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.

 You should have received a copy of the GNU General Public License
 along with the Bats Game.  If not, see <http://www.gnu.org/licenses/>.
 */
package bats.map;

import bats.scene.SceneNode;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.joints.HingeJoint;
import com.jme3.bullet.objects.PhysicsRigidBody;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import java.util.logging.Logger;
import jme3utilities.physics.SimpleSolidControl;

/**
 * A simple solid control for a lockable door with plenty of hinge friction.
 * <p>
 * When unlocked, the door will move if pushed but will not swing freely.
 * <p>
 * Each instance is enabled at creation.
 *
 * @author Stephen Gold <sgold@sonic.net>
 */
public class DoorControl
        extends SimpleSolidControl {
    // *************************************************************************
    // constants

    /**
     * default coefficient of friction (&ge;0)
     */
    final private static float friction = 5f;
    /**
     * message logger for this class
     */
    final private static Logger logger =
            Logger.getLogger(DoorControl.class.getName());
    // *************************************************************************
    // fields
    /**
     * lower limit on hinge angle when unlocked: initialized by constructor (in
     * radians)
     */
    private float lowerLimit;
    /**
     * upper limit on hinge angle when unlocked: initialized by constructor (in
     * radians)
     */
    private float upperLimit;
    /**
     * which scene the door resides in: set by constructor
     */
    final private SceneNode scene;
    // *************************************************************************
    // constructors

    /**
     * Instantiate an enabled control with the specified collision shape and
     * mass.
     *
     * @param shape the door's collision shape (not null)
     * @param mass the door's mass (in kilograms, &gt;0)
     * @param scene which scene the door is in (not null)
     * @param lowerLimit minimum hinge rotation angle (in radians)
     * @param upperLimit maximum hinge rotation angle (in radians)
     */
    public DoorControl(CollisionShape shape, float mass, SceneNode scene,
            float lowerLimit, float upperLimit) {
        super(true, shape, mass, scene.getWorld().getPhysics().getSpace());
        assert mass > 0f : mass;
        /*
         * Save the hinge angle limits.  They cannot be applied until
         * the door has been hung on a hinge.
         */
        assert lowerLimit < upperLimit : lowerLimit;
        this.lowerLimit = lowerLimit;
        this.upperLimit = upperLimit;

        this.scene = scene;

        assert isEnabled();
    }
    // *************************************************************************
    // new methods exposed

    /**
     * Mount the door on a fixed object by means of a new hinge.
     *
     * @param axis axis of rotation of the hinge (unit vector, unaffected)
     * @param pivotLocation location of the pivot in the fixed object's
     * coordinate system (not null, unaffected)
     * @param fixed the fixed object (not null)
     */
    public void mount(Vector3f axis, Vector3f pivotLocation, Spatial fixed) {
        assert axis != null;
        assert axis.isUnitVector() : axis;
        assert pivotLocation != null;

        PhysicsRigidBody fixedObject = fixed.getControl(RigidBodyControl.class);
        assert fixedObject != null;

        float scaleFactor = scene.getScaleFactor();

        Vector3f worldPivot = fixed.localToWorld(pivotLocation, null);
        Vector3f doorPivot = spatial.worldToLocal(worldPivot, null);
        HingeJoint hinge = new HingeJoint(fixedObject, this,
                pivotLocation.divide(scaleFactor),
                doorPivot.divide(scaleFactor),
                axis.clone(), axis.clone());
        space.add(hinge);
    }

    /**
     * Alter the locked status (and hinge angle limits) of this door.
     *
     * This method mustn't be called before the hinge is added.
     *
     * @param newState true to lock the door, false to unlock it
     */
    public void setLocked(boolean newState) {
        assert joints.size() == 1;
        HingeJoint hinge = (HingeJoint) joints.get(0);

        if (newState) {
            /*
             * lock the door
             */
            hinge.setLimit(-0.01f, 0.01f);

        } else {
            /*
             * unlock the door
             */
            hinge.setLimit(lowerLimit, upperLimit);
        }
    }
    // *************************************************************************
    // SimpleSolidControl methods

    /**
     * (Re-)Initialize this control each time it is added.
     */
    @Override
    public void onAdd() {
        super.onAdd();

        setFriction(friction);
    }

    /**
     * De-initialize this control each time it is removed.
     *
     * Meant to be overridden.
     */
    @Override
    public void onRemove() {
        assert joints.size() == 1;
        HingeJoint hinge = (HingeJoint) joints.get(0);
        space.remove(hinge);

        super.onRemove();
    }

    /**
     * Method invoked before each physics tick to keep the door from swinging
     * too freely.
     *
     * @param unused
     * @param unused2
     */
    @Override
    public void prePhysicsTick(PhysicsSpace unused, float unused2) {
        if (spatial == null || !isEnabled() || !scene.isEnabled()) {
            return;
        }
        assert joints.size() == 1 : joints.size();
        HingeJoint hinge = (HingeJoint) joints.get(0);
        float hingeAngle = hinge.getHingeAngle();
        /*
         * If the hinge angle is within its limits, kill all motion.
         */
        if (hingeAngle > hinge.getLowerLimit()
                && hingeAngle < hinge.getUpperLimit()) {
            setAngularVelocity(new Vector3f());
            setLinearVelocity(new Vector3f());
        }
    }
}
